//#include <QtCore/QCoreApplication>

#include <iostream>

#include <QtCore>

//#include "include/licensing.h"
#include <qca_core.h>
#include <qca_publickey.h>
#include <string>
#include <algorithm>
#include <cctype>
#include <limits>
#include <QtCore>
#include "../../include/licensinguserinfo.h"

namespace
{


	char toLower(char ch)
	{
		return std::tolower(ch);
	}


	QByteArray loadPrivateKey(QString pathToKey)
	{
		QFile file(pathToKey);
		file.open(QFile::ReadOnly);

		return file.readAll();
	}


	QString toString(QCA::ConvertResult result)
	{
		switch (result)
		{
		case QCA::ConvertGood:
			return "ConvertGood";
		case QCA::ErrorDecode:
			return "ErrorDecode";
		case QCA::ErrorPassphrase:
			return "ErrorPassphrase";
		case QCA::ErrorFile:
			return "ErrorFile";
		}
		return "Unknown";
	}


	QString combinePath(QString dir, QString filename)
	{
		if (!(dir.endsWith('/') || (dir.endsWith('\\'))))
			dir += '/';

		return dir + filename;
	}


} //private namespace

const QString privateKeyName = "mu_key";
const QString publicKeyName = "mu_key.pub";
const QString keysFlag = "--keys";



bool generateKeys(QCA::PrivateKey& privateKey, QCA::PublicKey& publicKey);
bool generateKeys(std::ostream& os);
std::string readPassphrase();


int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		std::cerr << "To generate new private/public key pair use the following command: licenseGen "
		          + keysFlag.toStdString() << std::endl;

		std::cerr << "To generate a new license provide the license request as a command line argument" << std::endl;
		std::cerr << "e.g." << std::endl;
		std::cerr << "licenseGen <license request generated by Mu software>" << std::endl;
		return 1;
	}

	QCA::Initializer init;

	QCoreApplication app(argc, argv);

	//key pair generation
	if (app.arguments().contains(keysFlag))
	{
		generateKeys(std::cout);
		return 0;
	}


	QString licenseRequest = argv[1];
	//QString macAddress = "00:00:00:00:00:00"; //for debugging

	//QString licenseRequest = "1bf182b2e69011805aeadab72af58b9a808654a101b0418fd36d7f0f7e980ec1c60944f3de3027e855f1a16c9a29464527e5fc2f2bc09a9244514536c27c7652aa8f357b97fd6e89619213b4b533b8ee7db7cb14515c3d58084b0a5ce25125a0146ad52f738495f7e3173b43dd6434599ceb9e0a8fbb6662d5c3c222708fd2455d14742773747a211016085ccab4ca06eb316bc4a55a1fd192d3bf5c18bb5387c7cf0ed6bca1a5d07fb7f0f85035895dffe78008eedfae52ace2cbb049304b2a6955771c853450a6890a3968b610cef13283369ba6f4a15f29a1ef6ac165f114958269f5a7befe8d1b6914df1c2096488f5ba6aad7b0d4e4475a07f9cdb53dc4";

	//License generation
	QString privateKeyPath = combinePath(QCoreApplication::applicationDirPath(), privateKeyName);

	if (!QFile::exists(privateKeyPath))
	{
		std::cerr << "Private key \"" << qPrintable(privateKeyPath) << "\" not found" << std::endl;
		//std::cerr << "New key pair generation..." << std::endl;

		std::cerr << "Would you like to generate a new key pair (y/n): ";
		char choice;
		std::cin >> choice;
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

		if (choice != 'y' && choice != 'Y')
			return 1;

		if (!generateKeys(std::cerr))
			return 1;

		std::cerr << std::endl;
		std::cerr << "License generation..." << std::endl;
	}

	bool continuing = true;
	int attamptNo = 0;
	QCA::SecureArray passPhrase = "";
	QCA::PrivateKey privateKey;

	while (continuing)
	{
		QCA::ConvertResult conversionResult;

		QByteArray byteArray = loadPrivateKey(privateKeyPath);

		privateKey = QCA::PrivateKey::fromPEM(byteArray, passPhrase, &conversionResult);

		//NOTE std::cerr is used for user coould redirect std::cout freely without risk to waste the output
		switch (conversionResult)
		{

		case QCA::ConvertGood:
		{
			continuing = false;
			break;
		}

		case QCA::ErrorFile:
		{
			std::cerr << "Incorrect private key file format" << std::endl;
			return 1;
		}

		case QCA::ErrorPassphrase:
		case QCA::ErrorDecode:
		{
			++attamptNo;
			if (attamptNo == 1)
			{
				std::cerr << "Enter the pass phrase: ";
			}
			else
			{
				std::cerr << "Incorrect pass phrase, try again or type \"quit\" to exit: ";
			}

			std::string pwd = readPassphrase();
			passPhrase = QCA::SecureArray(pwd.c_str()); //saving new pass phrase before transforming it

			std::transform(pwd.begin(), pwd.end(), pwd.begin(), toLower);
			if (pwd == "quit")
				return 0;

			break;
		}

		} //switch

	} //while continuing


	QCA::SecureArray input = QByteArray::fromHex(licenseRequest.toAscii());
	QCA::SecureArray output;

	if (!privateKey.decrypt(input, &output, QCA::EME_PKCS1_OAEP))
	{
		std::cerr << "Unable to decrypt the license request given" << std::endl;
		return 1;
	}

	QDomDocument doc;
	doc.setContent(output.toByteArray());

	LicensingUserInfo lui;
	lui.readFrom(doc);

	std::string indent = "   ";
	std::cout << "license request:" << std::endl;
	std::cout << indent << "hostname: " << lui.hostName().toStdString() << std::endl;
	std::cout << indent << "OS: " << lui.OSName().toStdString() << std::endl;
	std::cout << indent << "username: " << lui.userName().toStdString() << std::endl;
	std::cout << indent << "utc: " << lui.timeUtc().toString().toStdString() << std::endl;
	foreach (QString macAddr, lui.macAddresses())
		std::cout << indent << macAddr.toStdString() << std::endl;

	if (lui.macAddresses().isEmpty())
	{
		std::cout << indent << "no MAC address given in the request" << std::endl;
		return 1;
	}

	std::cout << std::endl;
	std::cout << "license (for 1st Mac):" << std::endl;

	//taking first MAC address and generating license for it
	QString macAddress = lui.macAddresses().front();

	QCA::SecureArray sa = macAddress.toLower().toAscii();
	QByteArray license = privateKey.signMessage(sa, QCA::EMSA3_MD5);

	std::cout << QString(license.toHex()).toStdString() << std::endl;
}


bool generateKeys(std::ostream& os) {
	QCA::PrivateKey privateKey;
	QCA::PublicKey publicKey;

	if (!generateKeys(privateKey, publicKey))
		return false;

	os << "Enter pass phrase, empty for no passphrase: ";

	QCA::SecureArray passPhrase = QCA::SecureArray(readPassphrase().c_str());

	QString privateKeyPath = combinePath(QCoreApplication::applicationDirPath(), privateKeyName);
	QString publicKeyPath = combinePath(QCoreApplication::applicationDirPath(), publicKeyName);

	QFile::remove(privateKeyPath);
	QFile::remove(publicKeyPath);

	if (!privateKey.toPEMFile(privateKeyPath, passPhrase))
	{
		os << "unable to save your private key to the following file: " + privateKeyPath.toStdString() << std::endl;
		return false;
	}
	os << "Your private key has been saved to \"" + privateKeyPath.toStdString() +"\" file successfully" << std::endl;

	if (!publicKey.toPEMFile(publicKeyPath))
	{
		os << "unable to save your public key to the following file: " + publicKeyPath.toStdString() << std::endl;
		return false;
	}
	os << "Your public key has been saved to \"" + publicKeyPath.toStdString() +"\" file successfully" << std::endl;

	return true;
}

bool generateKeys(QCA::PrivateKey& privateKey, QCA::PublicKey& publicKey)
{
	if(!QCA::isSupported("pkey") ||
	        !QCA::PKey::supportedIOTypes().contains(QCA::PKey::RSA))
	{
		std::cerr << "RSA not supported" << std::endl;
		std::cerr << "libqca2-plugin-ossl most probably missing" << std::endl;
		return false;
	}
	else
	{
		privateKey = QCA::KeyGenerator().createRSA(2048);

		if (privateKey.isNull())
		{
			std::cerr << "Unable to generate the private key" << std::endl;
			return false;
		}

		publicKey = privateKey.toPublicKey();

		return true;
	}
}

std::string readPassphrase()
{
	std::string s;

	std::getline(std::cin, s);

	return s;
}
